#!/usr/bin/env python
# -*-*- encoding: utf-8 -*-*-
#
#
# $Id: picasapush.py 169 2007-11-30 02:12:47Z havard.gulldahl $
#
# Copyright 2007-2008 Håvard Gulldahl
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""A desktop client for PicasaWeb services."""

__author__ = u'havard@gulldahl.no (Håvard Gulldahl)'

import sys, os.path, StringIO, urllib, mimetypes, time, logging
try:
  from picasapush.gui import gui, gui_dialog, gui_rc
except ImportError:
  from gui import gui, gui_dialog, gui_rc
from PyQt4 import QtCore, QtGui, uic

import atom
atom.XML_STRING_ENCODING=None # workaround bug

import gdata.photos
import gdata.photos.service

from picasapush.pushermodel import PicasaItemModel
from picasapush.pusheragent import PicasaAgent, PicasaLoginThread #, PicasaAgentRunner

class PicasaPush(QtGui.QMainWindow):
  authenticated = False # logged in ?
  timers = {}
  lastQuery = None # the last query executed
  entryShown = None # the picture currently shown

  def __init__(self, model):

    QtGui.QMainWindow.__init__(self)
    self.picasaModel = model

    # Set up the user interface from Designer.
    self.ui = gui.Ui_PPWindow()
    self.ui.setupUi(self)
    #self.ui = uic.loadUi('gui/gui.ui')
    self.show()

    self.ui.picasaTree.setDragEnabled(True)
    self.ui.picasaTree.setAcceptDrops(True)
    self.ui.picasaTree.setDropIndicatorShown(True)
    self.ui.picasaTree.header().hide()
    self.ui.loginBox.hide()
    self.ui.scaleBox.hide()

    ### set up model
    self.ui.picasaTree.setModel(model)
    self.selection= self.ui.picasaTree.selectionModel()
    self.connect(self.selection, QtCore.SIGNAL('selectionChanged(const QItemSelection&, const QItemSelection&)'), self.picasaSelect)

    ### set up throbber
    self.loading = QtGui.QMovie('loading.gif')
    self.ui.activityIndicator.setMovie(self.loading)

    ### set up search filter
    for (key, desc) in {'public': 'Public'}.iteritems():
      self.ui.filterMethod.addItem(desc, QtCore.QVariant(key))

    ### connect gui signals
    self.connect(self.ui.picasaLogin, QtCore.SIGNAL('clicked()'), self.picasaLogin)
    self.connect(self.ui.picasaNewAlbum, QtCore.SIGNAL('clicked()'), self.picasaNewAlbum)
    self.connect(self.ui.picasaDelete, QtCore.SIGNAL('clicked()'), self.picasaDelete)
    self.connect(self.ui.picasaShowOwn, QtCore.SIGNAL('clicked()'), self.showOwnPictures)
    #self.connect(self.ui.filterMethod, QtCore.SIGNAL('editTextChanged (const QString &)'), self.picasaCheckMethod)
    self.connect(self.ui.filterQuery, QtCore.SIGNAL('editingFinished()'), self.picasaQuery)
    self.connect(self.ui.filterQuery, QtCore.SIGNAL('textChanged(QString)'), self.picasaDelayedQuery)
    self.connect(self.ui.picTitle, QtCore.SIGNAL('editingFinished()'), self.picasaUpdateTitle)
    self.connect(self.ui.picLocation, QtCore.SIGNAL('editingFinished()'), self.picasaUpdateLocation)
    self.connect(self.ui.picSummary, QtCore.SIGNAL('textChanged()'), self.picasaUpdateSummary)
    self.connect(self.ui.picDateTime, QtCore.SIGNAL('editingFinished()'), self.picasaUpdateDatetime)
    self.connect(self.ui.picTags, QtCore.SIGNAL('editTextChanged (const QString &)'), self.picasaUpdateTags)
    self.connect(self.ui.activityIndicator, QtCore.SIGNAL('clicked()'), self.throbberClicked)

    ### connect custom signals
    self.connect(self, QtCore.SIGNAL('error(PyQt_PyObject)'), self.picasaError)
    self.connect(self, QtCore.SIGNAL('loading()'), self.startLoading)
    self.connect(self, QtCore.SIGNAL('finishedLoading()'), self.stopLoading)
    #self.connect(self.ui.picasaTree, QtCore.SIGNAL('insertPhoto(PyQt_PyObject,PyQt_PyObject)'), self

  def picasaError(self, msg):
    print "ERROR:", msg

  def updateView(self):
    print "updating view"
    self.picasaModel.emit(QtCore.SIGNAL('layoutChanged()'))

  def showOwnPictures(self):
    self.emit(QtCore.SIGNAL('getAlbums()'))

  def picasaLogin(self):
    #print "In picasaLogin in thread", QtCore.QThread.currentThread()
    n = unicode(self.ui.picasaUsername.text())
    p = unicode(self.ui.picasaPassword.text())
    if not n and not p:
      try:
        n, p = file('/home/havard/.gmailpass').read().split(',')
      except IOError, (e):
        print e
    if n and p:
      self.emit(QtCore.SIGNAL('login(PyQt_PyObject,PyQt_PyObject)'), n, p)

  def picasaLoggedIn(self, authenticated):
    print "picasaLoggedIn:", authenticated
    if authenticated:
      self.authenticated = True
      self.ui.loginFrame.hide()
      #self.ui.searchFrame.show()
      self.ui.scaleFrame.setEnabled(True)
      self.ui.picasaNewAlbum.setEnabled(True)
      self.ui.picasaDelete.setEnabled(True)
      self.ui.picasaShowOwn.setEnabled(True)
      for (key, desc) in {'user': 'My own', 'contact': 'My friends\''}.iteritems():
        self.ui.filterMethod.addItem(desc, QtCore.QVariant(key))
      if not self.picasaModel.root.children:
        self.emit(QtCore.SIGNAL('getAlbums()'))

  def picasaNewAlbum(self):
    print 'picasaNewAlbum'
    self.newDialog = QtGui.QDialog(self)
    #self.newDialog.ui = gui_dialog.Ui_Dialog()
    self.newDialog.ui = uic.loadUi('gui-dialog.ui', self.newDialog)
    self.newDialog.ui.show()
    #self.newDialog.setupUi(self.newDialog)
    #self.newDialog.ui.setWindowTitle(u'New album details')
    if self.newDialog.exec_(): # exec_() returns 0 on 'Cancel', 1 on 'OK'
      # album name and summary entered
      albumTitle = unicode(self.newDialog.ui.title.text())
      albumSummary = unicode(self.newDialog.ui.summary.text())
      #print "adding album %s (%s)" % (albumTitle, albumSummary)
      self.emit(QtCore.SIGNAL('insertAlbum(PyQt_PyObject,PyQt_PyObject)'), albumTitle, albumSummary)

  def picasaDelete(self):
    print 'picasaDelete'
    try:
      selected = self.ui.picasaTree.selectedIndexes()[0]
    except IndexError: # nothing selected
      return
    print "deleting", selected.internalPointer().entry.title.text
    item = selected.internalPointer()
    self.emit(QtCore.SIGNAL('deleteItem(PyQt_PyObject)'), item)

  def deleted(self, item):
    print 'deleted', item

    item.parent.removeChild(item)
    self.picasaModel.emit(QtCore.SIGNAL("layoutChanged()"))
    self.ui.picasaTree.clearSelection()

  def picasaSelect(self, selected, deselected):
    #print 'picasaSelect', selected, selected.indexes
    if not selected.indexes():
      return
    entry = selected.indexes()[0].internalPointer().entry
    if isinstance(entry, gdata.photos.PhotoEntry):
      self.setPhoto(entry)
      bigthumb = GetLargestThumbnail(entry.media.thumbnail)
      self.emit(QtCore.SIGNAL('getThumbnail(PyQt_PyObject)'), bigthumb.url)

  def setPhoto(self, entry):
    #self.startLoading()
    self.entryShown = entry
    try:
      editable = hasattr(entry.GetEditLink(), 'href')
    except AttributeError, e:
      print e
      editable = False
    self.ui.picasaPic.entry = entry

    for w in (self.ui.picLocation, self.ui.picTitle, self.ui.picSummary, self.ui.picDateTime,
              self.ui.picTags):
      w.setEnabled(editable)
    self.ui.picTitle.setText(unicode(entry.title.text, 'utf8'))
    if entry.summary.text is not None:
      self.ui.picSummary.setText(unicode(entry.summary.text, 'utf8'))
    else:
      self.ui.picSummary.setText('')
    try:
      dt = QtCore.QDateTime()
      dt.setTime_t(int(entry.timestamp.text)/1000)
      self.ui.picDateTime.setDateTime(dt)
    except AttributeError:
      self.ui.picDateTime.setDateTime(QtCore.QDateTime.fromTime_t(1))
    except Exception, e:
      self.picasaError(e.message)
    self.ui.picTags.clear()
    if entry.geo.location():
      self.ui.picLocation.setText('%s x %s' % entry.geo.location())
    else:
      self.ui.picLocation.setText('')
    try:
      if entry.media.keywords.text is not None:
        self.ui.picTags.addItems( [ unicode(s.strip(), 'utf8') for s in entry.media.keywords.text.split(",") ] )
    except AttributeError: # keywords is None (has no attributes)
      pass
    except Exception, e:
      self.picasaError(e.message)
    try:
      tt = u'Size: %(h)s x %(w)s - %(s)s kbytes.\nUploaded by:%(u)s.\n%(c)s comments.' % \
        { 'd': time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(int(entry.timestamp.text)/1000)),
          'h': entry.height.text,
          'w': entry.width.text,
          's': int(entry.size.text)/1024,
          'c': entry.commentCount.text,
          'u': entry.media.credit.text,
        }
      loc = entry.geo.location()
      print loc
      if loc:
        tt += '\nLatitude: %s, longtitude: %s ' % loc
      self.ui.picasaPic.setToolTip(tt)
    except Exception, e:
        print e

  #def picasaCheckMethod(self, scope):
    #print "picasAcheckMethod", scope
    #if not len(unicode(scope).strip()):
      #self.ui.filterMethod.setCurrentIndex(0)
      #return
    #item = QtCore.QVariant(scope)
    #if self.ui.filterMethod.findText(item, QtCore.Qt.MatchFixedString) == -1 : # string not found in list
      #self.ui.filterMethod.addItem(scope, item) # adding username to the list

  def picasaQuery(self):
    try:
      self.timers[self.picasaQuery].stop()
    except:
      pass
    #if not self.authenticated:
      #self.emit(QtCore.SIGNAL('error(PyQt_PyObject)'), 'not logged in')
      #return False

    q = unicode(self.ui.filterQuery.text())
    #if q == self.lastQuery:
      #return False
    #scope = unicode(self.ui.filterMethod.itemData(self.ui.filterMethod.currentIndex()).toString())
    scope = unicode(self.ui.filterMethod.currentText())
    idx = self.ui.filterMethod.findText(scope, QtCore.Qt.MatchFixedString)
    if idx != -1 : # string found in list
      scope = unicode(self.ui.filterMethod.itemData(idx).toString()) # get data
    else:
      # 'scope' now contains a presumably valid username
      if len(q) == 0: # no search string, show all public photos of user
        self.ui.picasaTree.reset()
        self.emit(QtCore.SIGNAL('showUserPhotos(PyQt_PyObject)'), scope)
        return
    self.lastQuery = q
    self.ui.picasaTree.reset()
    self.emit(QtCore.SIGNAL('searchPhotos(PyQt_PyObject, PyQt_PyObject)'), scope, q)

  def picasaDelayedQuery(self, newtext):
    #print "picasaDelayedQuery", newtext
    if newtext:
      self.delay(self.picasaQuery, 2000)

  def picasaUpdateTitle(self):
    if self.entryShown is None: return
    newTitle = unicode(self.ui.picTitle.text())
    if newTitle == unicode(self.entryShown.title.text, 'utf8'):
      return #title is unchanged
    self.entryShown.title.text = newTitle
    self.emit(QtCore.SIGNAL('updatePhoto(PyQt_PyObject)'), self.entryShown)

  def picasaUpdateLocation(self):
    if self.entryShown is None: return
    loc = unicode(self.ui.picLocation.text())
    if not loc or loc == unicode(self.entryShown.geo.location()):
      return #loc is empty or unchanged
    newLat, newLon = [ float(z.strip()) for z in loc.split('x') ]
    self.entryShown.geo.set_location( (newLat, newLon) )
    self.emit(QtCore.SIGNAL('updatePhoto(PyQt_PyObject)'), self.entryShown)

  def picasaUpdateSummary(self):
    if self.entryShown is None: return
    self.delay(self.doUpdateSummary, 2000)

  def doUpdateSummary(self):
    newsummary = unicode(self.ui.picSummary.toPlainText())
    #print newsummary, unicode(self.entryShown.summary.text, 'utf8')
    if newsummary == unicode(self.entryShown.summary.text, 'utf8'):
      return #summary is unchanged
    self.entryShown.summary.text = newsummary
    self.emit(QtCore.SIGNAL('updatePhoto(PyQt_PyObject)'), self.entryShown)

  def picasaUpdateDatetime(self):
    if self.entryShown is None: return
    newEpoch = int(self.ui.picDateTime.dateTime().toTime_t())
    try:
      if newEpoch/60 == int(self.entryShown.timestamp.text)/(1000*60):
        return #timestamp is unchanged, within 60 secs
    except TypeError:
      pass
    except AttributeError: #.timestamp is None
      self.entryShown.timestamp = gdata.photos.Timestamp()
    #newDT = self.ui.picDateTime.datetime()
    #if newDT.secsTo(QtCore.QDateTime(newDT.date())) == 0:
      #return # still within the same day
    #print "new daytime", newDT
    #newEpoch = str(newDT.toTime_t()*1000)
    self.entryShown.timestamp.text = str(newEpoch*1000)
    print "updating timestamp"
    self.emit(QtCore.SIGNAL('updatePhoto(PyQt_PyObject)'), self.entryShown)

  def picasaUpdateTags(self):
    if self.entryShown is None: return
    self.delay(self.doUpdateTags, 2000)

  def doUpdateTags(self):
    newtag = unicode(self.ui.picTags.currentText())
    try:
      if newtag.lower() in unicode(self.entryShown.media.keywords.text, 'utf8').lower():
        return # tag already exists
    except AttributeError: # invalid lookup
      return
    except TypeError:
      pass
    print "adding tag", newtag
    self.emit(QtCore.SIGNAL('addTag(PyQt_PyObject,PyQt_PyObject)'), self.entryShown, newtag)

  def picasaUpdateGeo(self):
    if self.entryShown is None: return

  def delay(self, method, delay, *args, **kwargs):
    print self.timers
    try:
      self.timers[method].stop()
    except:
      pass

    t = QtCore.QTimer()
    t.setSingleShot(True)
    t.start(delay)
    self.connect(t, QtCore.SIGNAL('timeout()'), method)
    self.timers[method] = t


  #def doUpdateSearchResult(self, entry):
    #head = QtGui.QHeaderView(QtCore.Qt.Horizontal, self.ui.picasaTree)
    #self.ui.picasaTree.setHeader(
    #self.ui.picasaTree.header().hide()

  def setPic(self, data):
    self.ui.picasaPic.rawpix = QtGui.QPixmap()
    self.ui.picasaPic.rawpix.loadFromData(data)
    self.ui.picasaPic.setPixmap(self.ui.picasaPic.rawpix)

  def startLoading(self):
    #print "loading..."
    self.loading.start()

  def stopLoading(self):
    #print "not laoding..."
    self.loading.stop()

  def throbberClicked(self):
    self.emit(QtCore.SIGNAL('stop()'))


def GetLargestThumbnail(media_thumbnail_list):
  r = {}
  for thumb in media_thumbnail_list:
    r[int(thumb.width)*int(thumb.height)] = thumb
  keys = r.keys()
  keys.sort()
  return r[keys[-1]]



if __name__ == "__main__":
  DEBUG = '-d' in sys.argv
  if DEBUG:
    logging.basicConfig(level=logging.DEBUG)
  logging.debug('DEBUG mode = %s',DEBUG)
  app = QtGui.QApplication(sys.argv)
  thr = app.thread()
  print "main thread: ", thr
  model = PicasaItemModel()
  ui = PicasaPush(model)
  #ui.show()
  if DEBUG:
    from modeltest import ModelTest
    test = ModelTest(model, ui);
  agent = PicasaAgent()
  model.proxy = agent
  #QCon = QtCore.Qt.QueuedConnection
  app.connect(ui, QtCore.SIGNAL('login(PyQt_PyObject,PyQt_PyObject)'), agent.doLogin)
  app.connect(ui, QtCore.SIGNAL('login(PyQt_PyObject,PyQt_PyObject)'), ui.startLoading)
  app.connect(ui, QtCore.SIGNAL('getAlbums()'), agent.doGetAlbums)
  app.connect(ui, QtCore.SIGNAL('getAlbums()'), ui.startLoading)

  app.connect(ui, QtCore.SIGNAL('getThumbnail(PyQt_PyObject)'), agent.doGetThumbnail)
  app.connect(ui, QtCore.SIGNAL('getThumbnail(PyQt_PyObject)'), ui.startLoading)
  app.connect(ui.ui.picasaTree, QtCore.SIGNAL('insertPhoto(PyQt_PyObject,PyQt_PyObject)'), agent.doInsertPhoto)
  app.connect(ui.ui.picasaTree, QtCore.SIGNAL('insertPhoto(PyQt_PyObject,PyQt_PyObject)'), ui.startLoading)
  app.connect(ui, QtCore.SIGNAL('insertAlbum(PyQt_PyObject,PyQt_PyObject)'), agent.doInsertAlbum)
  app.connect(ui, QtCore.SIGNAL('insertAlbum(PyQt_PyObject,PyQt_PyObject)'), ui.startLoading)

  app.connect(ui, QtCore.SIGNAL('searchPhotos(PyQt_PyObject,PyQt_PyObject)'), agent.doSearch)
  app.connect(ui, QtCore.SIGNAL('searchPhotos(PyQt_PyObject,PyQt_PyObject)'), ui.startLoading)
  app.connect(ui, QtCore.SIGNAL('showUserPhotos(PyQt_PyObject)'), agent.doGetAlbums)
  app.connect(ui, QtCore.SIGNAL('showUserPhotos(PyQt_PyObject)'), ui.startLoading)
  app.connect(ui, QtCore.SIGNAL('updatePhoto(PyQt_PyObject)'), agent.doUpdatePhoto)
  app.connect(ui, QtCore.SIGNAL('updatePhoto(PyQt_PyObject)'), ui.startLoading)
  app.connect(ui, QtCore.SIGNAL('deleteItem(PyQt_PyObject)'), agent.doDelete)
  app.connect(ui, QtCore.SIGNAL('deleteItem(PyQt_PyObject)'), ui.startLoading)
  app.connect(ui, QtCore.SIGNAL('stop()'), ui.stopLoading)
  app.connect(ui, QtCore.SIGNAL('stop()'), agent.abort)

  app.connect(model, QtCore.SIGNAL('getPhotos(PyQt_PyObject)'), agent.doGetPhotos)
  app.connect(model, QtCore.SIGNAL('getPhotos(PyQt_PyObject)'), ui.startLoading)



  app.connect(agent, QtCore.SIGNAL('loggedIn(PyQt_PyObject)'), ui.picasaLoggedIn)
  app.connect(agent, QtCore.SIGNAL('loggedIn(PyQt_PyObject)'), ui.stopLoading)
  #app.connect(agent.login, QtCore.SIGNAL('loggedIn(PyQt_PyObject)'), ui.picasaLoggedIn)
  app.connect(agent, QtCore.SIGNAL('error(PyQt_PyObject)'), ui.picasaError)

  app.connect(agent, QtCore.SIGNAL('gotAlbums(PyQt_PyObject)'), model.doUpdateTopLevel)
  #app.connect(agent, QtCore.SIGNAL('gotAlbums(PyQt_PyObject)'), ui.doUpdateSearchResult)
  app.connect(agent, QtCore.SIGNAL('gotAlbums(PyQt_PyObject)'), ui.stopLoading)
  app.connect(agent, QtCore.SIGNAL('gotPhotos(PyQt_PyObject,PyQt_PyObject)'), model.doUpdateBranch)
  app.connect(agent, QtCore.SIGNAL('gotPhotos(PyQt_PyObject,PyQt_PyObject)'), ui.stopLoading)
  app.connect(agent, QtCore.SIGNAL('gotThumbnail(PyQt_PyObject)'), ui.setPic)
  app.connect(agent, QtCore.SIGNAL('gotThumbnail(PyQt_PyObject)'), ui.stopLoading)
  app.connect(agent, QtCore.SIGNAL('insertedPhoto()'), ui.updateView)
  app.connect(agent, QtCore.SIGNAL('insertedPhoto()'), ui.stopLoading)
  app.connect(agent, QtCore.SIGNAL('insertedAlbum()'), agent.doGetAlbums)
  app.connect(agent, QtCore.SIGNAL('insertedAlbum()'), ui.startLoading)
  app.connect(agent, QtCore.SIGNAL('gotSearchResult(PyQt_PyObject)'), model.doUpdateTopLevel)
  app.connect(agent, QtCore.SIGNAL('gotSearchResult(PyQt_PyObject)'), ui.stopLoading)
  app.connect(agent, QtCore.SIGNAL('gotUpdatedPhoto(PyQt_PyObject)'), ui.setPhoto)
  app.connect(agent, QtCore.SIGNAL('gotUpdatedPhoto(PyQt_PyObject)'), ui.stopLoading)

  app.connect(agent, QtCore.SIGNAL('deleted(PyQt_PyObject)'), ui.stopLoading)
  app.connect(agent, QtCore.SIGNAL('deleted(PyQt_PyObject)'), ui.deleted)



  sys.exit(app.exec_())
